//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/mem/lsegalloc.h"
#include "sagittarius/io/io.h"
#include <malloc.h>

/// lx_newseg
void lx_newseg(lx_segcontext* ctx) {
    size_t len;
    void* a=NULL;
    ctx->step <<= 2;
    len = ctx->step;
    /*SG_WRITE(1, "lx_newseg: len = ");
    sg_writeUInt(1, len);
    SG_WRITE(1, "\n");*/
    AT(len < 2000000);
    a = malloc(len);
    AN(a, "malloc failed");
    
    len -= sizeof(lx_seglink);
    ctx->end = (ctx->end->next = (void*)((uint8*)a+len));
    ctx->end->next = NULL;
    
    len -= sizeof(lx_seg);
    ctx->end->seg = (void*)((uint8*)a+len);
    ctx->end->seg->p = ctx->end->seg->start = a;
    ctx->end->seg->stop = (void*)((uint8*)a+len);
    /*SG_WRITE(1, "lx_newseg: ctx->end->seg->stop = ");
    sg_writePtr(1, ctx->end->seg->stop);
    SG_WRITE(1, "\n");*/
}

/// lx_nextseg
void lx_nextseg(lx_segcontext* ctx) {
    if(ctx->end->next)
        ctx->end = ctx->end->next;
    else
        lx_newseg(ctx);
}

/// lx_initctx
void lx_initsegctx(lx_segcontext* ctx) {
    ctx->step=4096;
    {
        size_t len=ctx->step;
        void* a = malloc(len);
        
        len -= sizeof(lx_seglink);
        ctx->beg = ctx->end = (void*)((uint8*)a+len);
        /*SG_WRITE(1, "lx_initsegctx: ctx->end = ");
        sg_writePtr(1, ctx->end);
        SG_WRITE(1, "\n");*/
        ctx->end->next = NULL;
        
        len -= sizeof(lx_seg);
        ctx->end->seg = (void*)((uint8*)a+len);
        ctx->end->seg->p = ctx->end->seg->start = a;
        ctx->end->seg->stop = (void*)((uint8*)a+len);
    }
}

///lx_destroyseglink
void lx_destroyseglink(lx_seglink* l) {
    if(l->next)
        lx_destroyseglink(l->next);
    if(l->seg)
            free(l->seg->start);
    else
        AN(0);
}

/// lx_destroyctx
void lx_destroysegctx(lx_segcontext* ctx) {
    lx_seglink* l=ctx->beg;
    lx_destroyseglink(l);
}

/// lx_segalloc
void* lx_segalloc(lx_segcontext* ctx, size_t bytes) {
    /*SG_WRITE(1, "lx_segalloc: ctx = ");
    sg_writePtr(1, ctx);
    SG_WRITE(1, "\nlx_segalloc: ctx->end = ");
    sg_writePtr(1, ctx->end);
    SG_WRITE(1, "\nlx_segalloc: ctx->end->seg = ");
    sg_writePtr(1, ctx->end->seg);
    SG_WRITE(1, "\n");*/
    /*SG_WRITE(1, "lx_segalloc: bytes = ");
    sg_writeUInt(1, bytes);
    SG_WRITE(1, "\nlx_segalloc: ctx->step = ");
    sg_writeUInt(1, ctx->step);
    SG_WRITE(1, "\n");*/
    uint8* p = ctx->end->seg->p;
    while((void*)(p+bytes) > ctx->end->seg->stop) {
        //SG_WRITE(1, "doing it again\n");
        lx_nextseg(ctx);
        p = ctx->end->seg->p;
        /*SG_WRITE(1, "lx_segalloc: ctx->end->seg->stop = ");
        sg_writePtr(1, ctx->end->seg->stop);
        SG_WRITE(1, "\nlx_segalloc: p = ");
        sg_writePtr(1, p);
        SG_WRITE(1, "\nlx_segalloc: bytes = ");
        sg_writeUInt64(1, bytes);
        SG_WRITE(1, "\nlx_segalloc: p+bytes = ");
        sg_writePtr(1, p+(uint64)bytes);
        SG_WRITE(1, "\n");*/
    }
    AT(p+bytes <= (uint8*)ctx->end->seg->stop);
    ctx->end->seg->p = p+bytes;
    return p;
}

//State save/restore
/// lx_seg_savestate
lx_segstate lx_seg_savestate(lx_segcontext* ctx) {
	#if SAGITTARIUS_COMPILER != SAGITTARIUS_COMPILER_MSVC
    lx_segstate r = {.end=ctx->end, .p = ctx->end->seg->p};
	#else
		lx_segstate r;
		r.end = ctx->end;
		r.p = ctx->end->seg->p;
	#endif
    return r;
}

/// lx_seg_restorestate
void lx_seg_restorestate(lx_segcontext* ctx, lx_segstate state) {
    ctx->end = state.end;
    AT(ctx->end->seg->start <= state.p && state.p < ctx->end->seg->stop);
    ctx->end->seg->p = state.p;
}

//top-of-stack functions
lx_segcontext lx_myseg; //TODO: make thread-local
lx_segcontext lx_tmpseg; //TODO: make thread-local

/// lx_alloc
void* lx_alloc(size_t bytes) {
    /*SG_WRITE(1, "lx_alloc: lx_segstack_top = ");
    sg_writePtr(1, lx_segstack_top);
    SG_WRITE(1, "\nlx_alloc: *lx_segstack_top = ");
    sg_writePtr(1, *lx_segstack_top);
    SG_WRITE(1, "\n");*/
    /*SG_WRITE(1, "lx_alloc: request for ");
    sg_writeUInt64(1, bytes);
    SG_WRITE(1, " bytes\n");*/
    return lx_segalloc(*lx_segstack_top, bytes);
}

/// lx_alloc_savestate
lx_segstate lx_alloc_savestate() {
    return lx_seg_savestate(*lx_segstack_top);
}

/// lx_alloc_restorestate
void lx_alloc_restorestate(lx_segstate state) {
    lx_seg_restorestate(*lx_segstack_top, state);
}

#define LX_SEGSTACK_SIZE 64
lx_segcontext* lx_segstack[LX_SEGSTACK_SIZE];

//-- Stack functions --//
lx_segcontext** lx_segstack_top=lx_segstack-1; //TODO: make thread-local

/// lx_segstack_push
void lx_segstack_push(lx_segcontext* ctxptr) {
    if(lx_segstack_top+1 < lx_segstack+LX_SEGSTACK_SIZE)
        *(++lx_segstack_top) = ctxptr;
}

/// lx_segstack_pop
lx_segcontext* lx_segstack_pop() {
    if(lx_segstack_top-1 >= lx_segstack-1) //TODO: just sacrifice first element so we can never deference an invalid pointer
        return *(lx_segstack_top--);
    else {
        AN(0, "lx_segstack_pop: underflow");
        return NULL;
    }
}

/// lx_segstack_get
lx_segcontext* lx_segstack_get(uint32 depth) {
    if(lx_segstack_top - depth >= lx_segstack)
        return *(lx_segstack_top - depth);
    else {
        AN(0, "lx_segstack_get: underflow");
        return NULL;
    }
}

/*
/// lx_tmpalloc
void* lx_tmpalloc(size_t bytes) {
    return lx_segalloc(&lx_tmpseg, bytes);
}

/// lx_tmpsave
lx_segstate lx_tmpsave() {
    return lx_seg_savestate(&lx_tmpseg);
}

/// lx_tmprestore
void lx_tmprestore(lx_segstate state) {
    return lx_seg_restorestate(&lx_tmpseg, state);
}*/
